﻿' ----------------------------------------------------------------------------------
' Microsoft Developer & Platform Evangelism
' 
' Copyright (c) Microsoft Corporation. All rights reserved.
' 
' THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
' EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
' OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ----------------------------------------------------------------------------------
' The example companies, organizations, products, domain names,
' e-mail addresses, logos, people, places, and events depicted
' herein are fictitious.  No association with any real company,
' organization, product, domain name, email address, logo, person,
' places, or events is intended or should be inferred.
' ----------------------------------------------------------------------------------

Imports System.Globalization
Imports System.IO
Imports System.Net.Browser
Imports System.Text
Imports System.Xml.Linq
Imports Microsoft.Samples.WindowsPhoneCloud.StorageClient.Credentials

Friend Class CloudBlobUploader
    Implements ICloudBlobUploader

    Private Const ChunkSize As Long = 4194304

    Private fileStream As Stream
    Private dataLength As Long
    Private dataSent As Long

    Private useBlocks As Boolean
    Private currentBlockId As String
    Private blockIds As IList(Of String) = New List(Of String)()

    Private uploadUrl As String

    Public Sub New(ByVal fileStream As Stream, ByVal uploadUrl As String, ByVal credentials As IStorageCredentials)
        Me.fileStream = fileStream
        Me.uploadUrl = uploadUrl
        Me.StorageCredentials = credentials
        Me.dataLength = Me.fileStream.Length
        Me.dataSent = 0

        ' Upload the blob in smaller blocks if it's a "big" file.
        Me.useBlocks = (Me.dataLength - Me.dataSent) > ChunkSize
    End Sub

    Public Event UploadFinished As EventHandler(Of CloudOperationResponse(Of Boolean)) Implements ICloudBlobUploader.UploadFinished

    Public Property StorageCredentials() As IStorageCredentials

    Public Sub StartUpload() Implements ICloudBlobUploader.StartUpload
        Dim uriBuilder = New UriBuilder(Me.uploadUrl)

        ' Set a timeout query string parameter.
        uriBuilder.Query = String.Format(CultureInfo.InvariantCulture, "{0}{1}", uriBuilder.Query.TrimStart("?"c), If(String.IsNullOrEmpty(uriBuilder.Query), "timeout=10000", "&timeout=10000"))

        If Me.useBlocks Then
            ' Encode the block name and add it to the query string.
            Me.currentBlockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()))
            uriBuilder.Query = String.Format(CultureInfo.InvariantCulture, "{0}&comp=block&blockid={1}", uriBuilder.Query.TrimStart("?"c), Me.currentBlockId)
        End If

        ' With or without using blocks, we'll make a PUT request with the data.
        Dim request = CType(WebRequestCreator.ClientHttp.Create(uriBuilder.Uri), HttpWebRequest)
        request.Method = "PUT"
        request.BeginGetRequestStream(AddressOf Me.WriteToStreamCallback, request)
    End Sub

    Private Sub WriteToStreamCallback(ByVal asynchronousResult As IAsyncResult)
        Dim request = CType(asynchronousResult.AsyncState, HttpWebRequest)
        Dim requestStream = request.EndGetRequestStream(asynchronousResult)
        Dim buffer = New Byte(4095) {}
        Dim bytesRead = 0
        Dim tempTotal = 0
        Me.fileStream.Position = Me.dataSent

        bytesRead = Me.fileStream.Read(buffer, 0, buffer.Length)
        Do While (bytesRead <> 0) AndAlso (tempTotal + bytesRead < ChunkSize)
            requestStream.Write(buffer, 0, bytesRead)
            requestStream.Flush()

            Me.dataSent += bytesRead
            tempTotal += bytesRead
            bytesRead = Me.fileStream.Read(buffer, 0, buffer.Length)
        Loop

        requestStream.Close()

        If Me.StorageCredentials IsNot Nothing Then
            request.Headers("x-ms-version") = "2009-09-19"
            request.Headers("x-ms-blob-type") = "BlockBlob"

            Me.StorageCredentials.SignRequest(request, tempTotal)
        End If

        request.BeginGetResponse(AddressOf Me.ReadHttpResponseCallback, request)
    End Sub

    Private Sub ReadHttpResponseCallback(ByVal asynchronousResult As IAsyncResult)
        Try
            Dim request = CType(asynchronousResult.AsyncState, HttpWebRequest)
            Dim response = CType(request.EndGetResponse(asynchronousResult), HttpWebResponse)

            If Me.useBlocks Then
                Me.blockIds.Add(Me.currentBlockId)
            End If

            ' If there is more data, send another request.
            If Me.dataSent < Me.dataLength Then
                Me.StartUpload()
            Else
                Me.fileStream.Close()
                Me.fileStream.Dispose()

                If Me.useBlocks Then
                    ' Commit the blocks into the blob.
                    Me.PutBlockList()
                Else
                    If response.StatusCode = HttpStatusCode.Created Then
                        Me.RaiseUploadFinished(True, Nothing)
                    Else
                        Me.RaiseUploadFinished(False, New Exception(String.Format(CultureInfo.InvariantCulture, "Error uploading blob: {0}", response.StatusDescription)))
                    End If
                End If
            End If
        Catch exception As Exception
            Me.RaiseUploadFinished(False, exception)
        End Try
    End Sub

    Private Sub PutBlockList()
        Dim uriBuilder = New UriBuilder(Me.uploadUrl)
        uriBuilder.Query = String.Format(CultureInfo.InvariantCulture, "{0}{1}", uriBuilder.Query.TrimStart("?"c), If(String.IsNullOrEmpty(uriBuilder.Query), "comp=blocklist", "&comp=blocklist"))

        Dim request = CType(WebRequestCreator.ClientHttp.Create(uriBuilder.Uri), HttpWebRequest)
        request.Method = "PUT"

        ' x-ms-version is required for put block list
        request.Headers("x-ms-version") = "2009-09-19"

        request.BeginGetRequestStream(AddressOf Me.BlockListWriteToStreamCallback, request)
    End Sub

    Private Sub BlockListWriteToStreamCallback(ByVal asynchronousResult As IAsyncResult)
        Dim request = CType(asynchronousResult.AsyncState, HttpWebRequest)
        Dim requestStream = request.EndGetRequestStream(asynchronousResult)
        Dim document = New XDocument(New XElement("BlockList", From blockId In Me.blockIds Select New XElement("Uncommitted", blockId)))
        Dim writer = XmlWriter.Create(requestStream, New XmlWriterSettings With {.Encoding = Encoding.UTF8})
        Dim length = 0L

        document.Save(writer)
        writer.Flush()

        length = requestStream.Length
        requestStream.Close()

        If Me.StorageCredentials IsNot Nothing Then
            request.Headers("x-ms-version") = "2009-09-19"

            Me.StorageCredentials.SignRequest(request, length)
        End If

        request.BeginGetResponse(AddressOf Me.BlockListReadHttpResponseCallback, request)
    End Sub

    Private Sub BlockListReadHttpResponseCallback(ByVal asynchronousResult As IAsyncResult)
        Try
            Dim request = CType(asynchronousResult.AsyncState, HttpWebRequest)
            Dim response = CType(request.EndGetResponse(asynchronousResult), HttpWebResponse)

            If response.StatusCode = HttpStatusCode.Created Then
                Me.RaiseUploadFinished(True, Nothing)
            Else
                Me.RaiseUploadFinished(False, New Exception(String.Format(CultureInfo.InvariantCulture, "Error uploading blob: {0}", response.StatusDescription)))
            End If
        Catch exception As Exception
            Me.RaiseUploadFinished(False, exception)
        End Try
    End Sub

    Private Sub RaiseUploadFinished(ByVal response As Boolean, ByVal exception As Exception)
        RaiseEvent UploadFinished(Me, New CloudOperationResponse(Of Boolean)(response, If(StorageClientExceptionParser.ParseXmlWebException(TryCast(exception, WebException)), exception)))
    End Sub
End Class